package be.kdg.bombermanunlimited.audio.server;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.ListIterator;
import java.util.Map;

import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;

import org.tritonus.share.sampled.file.TAudioFileFormat;

import be.kdg.bombermanunlimited.client.CentralRegistry;
import be.kdg.bombermanunlimited.client.CentralRegistry.BombermanObjects;
import be.kdg.bombermanunlimited.client.network.NetworkFactory;
import be.kdg.bombermanunlimited.client.network.packet.AudioDataPacket;
import be.kdg.bombermanunlimited.client.network.packet.AudioInfoPacket;
import be.kdg.bombermanunlimited.client.network.packet.ConversionTools;

public class AudioServer {
	/**
	 * Deze klasse bevat de functionaliteit voor het streamen van mp3 bestanden
	 * over het ringnetwerk. Het lokale afspelen gebeurt eveneens via het
	 * netwerk, namelijk via de local loop.
	 * 
	 * @author Siegfried
	 */
	private File music;
	private AudioInputStream output;
	private int frameSize;
	private int frameCounter;
	private boolean playing = true;
	private TrackInfo info = new TrackInfo();
	@SuppressWarnings("rawtypes")
	private Map properties;
	private Float frameRate;
	private byte[] audioInfo;

	private ArrayList<String> trackFileNames;
	private ListIterator<String> trackIterator;
	private boolean serverEnabled = false;
	private byte id = 0;

	/**
	 * Constructor. Doorzoekt meteen de ingevoerde map naar mp3's. Als er
	 * hierbij iets foutloopt zal de server weigeren streams te starten wanneer
	 * de manager dit vraagt.
	 */
	public AudioServer() {
		try {
			scanFolder();
			if (trackFileNames.size() == 0) {
				throw new Exception("No tracks found");
			}
			serverEnabled = true;

		} catch (Exception e) {
			trackFileNames = null;
			trackIterator = null;
			serverEnabled = false;
		}
	}

	/**
	 * zet alle parameters terug op hun beginwaarde.
	 */
	private void shutdownServer() {
		output = null;
		frameCounter = 0;
		music = null;
		playing = false;
	}

	/**
	 * @return true als de server bestanden heeft om te streamen
	 */
	public boolean isServerEnabled() {
		return serverEnabled;
	}

	/**
	 * loadFile() laadt 1 mp3 in als audioInputStream. De audioInfo wordt
	 * eveneens uitgelezen (auteur, titel, etc.)
	 * 
	 * @param track
	 *            locatie en bestandsnaam van het in te laden track.
	 * @throws UnsupportedAudioFileException
	 *             Het bestand is een onbekend of niet ondersteund formaat.
	 * @throws IOException
	 *             Fout bij het uitlezen van de track.
	 */
	private void loadFile(String track) throws UnsupportedAudioFileException,
			IOException {
		frameCounter = 0;
		// System.out.println("[Audio Server] loading " + track);
		music = new File(track);
		output = AudioSystem.getAudioInputStream(music);

		AudioFileFormat baseFileFormat = AudioSystem.getAudioFileFormat(music);
		properties = ((TAudioFileFormat) baseFileFormat).properties();
		// http://www.javazoom.net/mp3spi/docs/doc1.9.4/javazoom/spi/mpeg/sampled/file/MpegAudioFileFormat.html
		frameSize = (Integer) properties.get("mp3.framesize.bytes");
		// System.out.println("Frame size: " + frameSize);

		info.setAuthor((String) properties.get("author"));
		info.setTitle((String) properties.get("title"));
		info.setDuration((Long) properties.get("duration"));
		info.setServerName(CentralRegistry.getRegistry().getString(
				BombermanObjects.USERNAME));
		// TODO check of VBR werkt

		frameRate = (Float) properties.get("mp3.framerate.fps");
		// serializeer de audioinfo tot een bytearray.
		audioInfo = ConversionTools.serialize(info);

	}

	/**
	 * Laadt het eerstvolgende bestand in dat in de map werd gevonden.
	 * 
	 * @throws Exception
	 *             indien er iets foutliep bij het uitlezen van het bestand of
	 *             wanneer er geen bestanden meer over zijn.
	 */
	private void loadNext() throws Exception {
		if (trackIterator != null) {
			if (!trackIterator.hasNext()) {
				trackIterator = trackFileNames.listIterator();
			}
			if (trackIterator.hasNext()) {
				String dirname = CentralRegistry.getRegistry().getString(
						BombermanObjects.MUSIC_DIRECTORY);
				loadFile(dirname + (String) trackIterator.next());
				return;
			} else {
				throw new Exception("List is empty.");
			}
		}
		throw new Exception("Error loading file list.");
	}

	/**
	 * Zoekt naar mp3's in de map die is opgeslagen in de MUSIC_DIRECTORY.
	 */
	private void scanFolder() {
		trackFileNames = new ArrayList<String>();
		String dirname = CentralRegistry.getRegistry().getString(
				BombermanObjects.MUSIC_DIRECTORY);
		File audioFolder = new File(dirname);
		FilenameFilter onlymp3 = new ExtensionFilter("mp3");
		String s[] = audioFolder.list(onlymp3);
		for (int i = 0; i < s.length; i++) {
			trackFileNames.add(s[i]);
		}
		// maak de iterator aan die gebruikt wordt in loadNext()
		trackIterator = trackFileNames.listIterator();
	}

	/**
	 * start het streamen van audio.
	 * 
	 * @param id
	 *            Het id dat de centrale server heeft meegegeven voor deze
	 *            stream. Dit wordt in elk pakket meegestuurd.
	 */
	public void startStream(byte id) {
		try {
			this.id = id;
			// laad een bestand.
			loadNext();
		} catch (Exception e1) {
			trackFileNames = null;
			trackIterator = null;
			serverEnabled = false;
			// error loading file, skip me
			// System.out.println("Error loading next Audio file, skipping turn.");
			return;
		}
		playing = true;
		frameCounter = 0;

		while (playing) {
			try {
				// hier wordt bepaald om de hoeveel tijd audiodatapakketten
				// worden verstuurd. Momenteel wordt dit aan 2x de framerate
				// gedaan.
				// Dit stuk zou nog beter moeten worden geoptimaliseerd. De
				// parameter die op 500 staat zou moeten worden afgestemd op de
				// vertragingen van het netwerk en verhoogd en verlaagd worden
				// in functie hoe vol de buffers zitten op de clients in het
				// ringnetwerk. Een systeem waarbij meldingen van te lage
				// buffers achterwaarts doorheen de ring propageert is hiervoor
				// een mogelijke oplossing.
				Thread.sleep((long) (500 / frameRate));
				// stuur om de 2.5 seconden (500*5ms) de audio info over het
				// ringnetwerk. Dit doen we om nieuwe nodes ook de nodige
				// informatie te geven. Dit zouden we eventueel ook enkel in het
				// begin kunnen doen en via addnode van de networklistener.
				if (frameCounter % (frameRate * 5) == 0) {
					sendAudioInfoPacket();
				}
				frameCounter++;
				// System.out.println(frameRate);
				sendNextAudioPacket();
				// System.out.println("Sending package");
			} catch (InterruptedException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * Stopt de stream
	 */
	public void stop() {
		playing = false;
	}

	/**
	 * Verzendt audioinfo over het ringnetwerk.
	 */
	void sendAudioInfoPacket() {
		AudioInfoPacket packet = new AudioInfoPacket(audioInfo);
		NetworkFactory.getNetworkFactory().getRingNetwork().sendData(packet);
		// System.out.println("[Audio] Track info sent");
	}

	/**
	 * Verzendt 1 frame van de audiodata over het ringnetwerk.
	 * 
	 * @throws IOException
	 *             fout bij het lezen van de buffer.
	 */
	void sendNextAudioPacket() throws IOException {
		if (output != null) {
			byte[] buffer = new byte[frameSize];
			int status = -1;
			try {
				status = output.read(buffer, 0, frameSize);
			} catch (Exception e) {
			}
			// status wordt op -1 gezet zodra de stream op is. Indien dit het
			// geval is mag de server uitgeschakeld worden. Indien dit niet zo
			// is, mag een pakket verstuurd worden.
			if (status != -1) {
				/*
				 * byte id = AudioDataPacket.NOT_LAST_BYTE; if (status !=
				 * frameSize) { id = AudioDataPacket.LAST_BYTE; }
				 */

				AudioDataPacket packet = new AudioDataPacket(buffer, id);
				frameCounter = frameCounter + status;
				NetworkFactory.getNetworkFactory().getRingNetwork()
						.sendData(packet);
			} else {
				// System.err.println("END OF AUDIO STREAM");
				shutdownServer();
			}
		}

	}
}
